package com.doublefish.doublefishrobot.utils.ble;

import android.content.Context;
import android.util.Log;

import com.doublefish.doublefishrobot.R;
import com.doublefish.doublefishrobot.utils.ToastUtil;
import com.doublefish.doublefishrobot.utils.ble.bean.BleMachineInfoBean;
import com.doublefish.doublefishrobot.utils.ble.bean.BleOffsetBean;
import com.doublefish.doublefishrobot.utils.ble.bean.BleResponseHandlerInfoBean;
import com.doublefish.doublefishrobot.utils.ble.bean.BleScriptStatusBean;
import com.doublefish.doublefishrobot.utils.ble.bean.CRCUtils;
import com.doublefish.doublefishrobot.utils.ble.bean.OnBleResponseHandler;
import com.doublefish.doublefishrobot.utils.ble.bean.OnBleWriteFileProgressListner;

import java.io.File;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by peak on 2024/1/2
 */
public class DFS8ProtocolManager {
    //控制指令
    private static int S8ControlCodeDefault = 0;               //默认 未设置
    private static int S8ControlCodeWriteHead = 0x01;             //头部写文件
    private static int S8ControlCodeWriteTrail = 0x02;            //尾部写文件
    private static int S8ControlCodeWriteFinish = 0x03;           //文件发送结束
    private static int S8ControlCodeDeleteFile = 0x04;            //删除文件
    private static int S8ControlCodeClearFileDir = 0x05;          //删除目录下所有文件

    private static int S8ControlCodeReadFile = 0x06;              //读文件内容
    private static int S8ControlCodeReadFileNames = 0x07;         //读目录下文件列表
    private static int S8ControlCodeGetMachineState = 0x08;       //读机器状态  开机后的发球时间，数量等
    private static int S8ControlCodeHeartBeat = 0x09;             //心跳
    private static int S8ControlCodeConnectMachine = 0x0A;       //连接发球机

    private static int S8ControlCodeRunScript = 0x0B;            //启动脚本
    private static int S8ControlCodeStopScript = 0x0C;           //停止发球
    private static int S8ControlCodePauseScript = 0x0D;          //暂停发球
    private static int S8ControlCodeResumeScript = 0x0E;         //恢复发球
    private static int S8ControlCodeGetMachineId = 0x0F;         // 获取机器id

    private static int S8ControlCodeGetMachineOffset = 0x10;     //读取机械偏移
    private static int S8ControlCodeSetMachineOffset = 0x11;     //设置机械偏移
    private static int S8ControlCodeSaveMachineOffset = 0x12;    //保存机械偏移
    private static int S8ControlCodeTestBall = 0x13;             //测试球
    private static int S8ControlCodeGetFirmware = 0x14;          //读取固件版本

    private static int S8ControlCodeGetMac = 0x15;               //读MAC
    private static int S8ControlCodeSetMac = 0x16;               //写MAC
    private static int S8ControlCodeDisConnectMachine = 0x17;    //断开发球机
    private static int S8ControlCodeRestartMachine = 0x18;       //重启发球机
    private static int S8ControlCodeGetScriptState = 0x19;       //获取发球机状态


    private static volatile DFS8ProtocolManager instance;
    private Context context;

    private int subPackSize = 20;  //数据包大小
    private byte[] machineID = new byte[2];   //机器id  2字节 通用id为0x0000  目前测试的机器为 0x0001
    private DFS8ProtocolMaker s8ProtocolMaker = new DFS8ProtocolMaker();

    private List<BleResponseHandlerInfoBean> responseHandlerInfoBeanList = new ArrayList<>();
    private OnBleWriteFileProgressListner onBleWriteFileProgressListner;

    //临时脚本的数据
    private boolean shouldRunLastScript = false;
    private int dir = 0;
    private String fileName;
    private int loopInterval;
    private int time;
    private int count;
    private boolean isTime;

    //分包的数据
    private int scriptIndex = 0;
    private List<String> scriptList;

    private int dataIndex = 0;
    private List<byte[]> dataList;


    public static DFS8ProtocolManager getInstance() {
        if (instance == null) {
            synchronized (DFS8ProtocolManager.class) {
                if (instance == null) {
                    instance = new DFS8ProtocolManager();
                }
            }
        }
        return instance;
    }

    public void initContext(Context context) {
        this.context = context;
    }

    public void setSubPackSize(int subPackSize) {
        this.subPackSize = subPackSize;
    }


    //region 发球机指令
    public void connectMachine() {
        byte[] b = s8ProtocolMaker.connectMachine();
        postData(b);
        Log.i("connectMachine", "connectMachine");
    }

    public void connectMachine(OnBleResponseHandler handler) {
        responseHandlerInfoBeanList.clear();

        int controlCode = S8ControlCodeConnectMachine;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.connectMachine();
        postData(b, controlCode);
        Log.i("connectMachine", "connectMachine");
    }

    public void disConnectMachine() {
        byte[] b = s8ProtocolMaker.disconnectMachine();
        postData(b);
    }

    //获取机器id
    public void getMachineId() {
        byte[] b = s8ProtocolMaker.getMachineId();
        postData(b);
        Log.i("getMachineId", "getMachineId");
    }

    //读取目录下的文件列表
    public void readFilesName(int dir, OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeReadFileNames;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.readFileName(dir);
        postData(b, controlCode);
        Log.i("readFileName", "readFileName");
    }

    //读取文件内容（脚本内容）
    public void readFile(int dir, String fileName, OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeReadFile;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.readFile(dir, fileName);
        postData(b, controlCode);
        Log.i("readFileName", "readFileName");
    }

    /**
     启动脚本
     @param dir  目录
     @param fileName 脚本名
     @param loopInterval 循环间隔 ms 默认1000
     @param isTime   时长还是次数
     @param time     时长
     @param count    次数
     */
    public void runScript(int dir, String fileName, int loopInterval, boolean isTime, int time, int count) {
        runScript(dir, fileName, loopInterval, isTime, time, count, null);
    }

    /**
     启动脚本
     @param dir  目录
     @param fileName 脚本名
     @param loopInterval 循环间隔 ms 默认1000
     @param isTime   时长还是次数
     @param time     时长
     @param count    次数
     */
    public void runScript(int dir, String fileName, int loopInterval, boolean isTime, int time, int count, OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeRunScript;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.runScript(dir, fileName, loopInterval, isTime, time, count);
        postData(b, controlCode);
        Log.i("runScript", "runScript");
    }

    //测试脚本 单球测试
    public void testBall(String script) {
//        String str = "0,25,#1,13-20,#2,25-30,12-20,#0,25,#3,12-20,15-20,30-40,#0,25,#0,50,";
        byte[] b = s8ProtocolMaker.testBall(script);
        postData(b);
    }

    /**
     写入脚本 (如果脚本足够长，就会分包，只写入头)
     @param dir      目录
     @param fileName 脚本名
     @param script   脚本内容
     */
    public void writeScript(int dir, String fileName, String script, OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeWriteFinish; //写完文件 才回调
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        this.shouldRunLastScript = false;
        this.dir = dir;
        this.fileName = fileName;

        //由于发球机缓冲区 只有256byte 所以对于脚本可能需要分包
        byte[] fileNameData = stringToBytes(fileName);
        List<String> list = splitScript(script, fileNameData.length);
        if (list.size() == 0) {
            return;
        }

        this.scriptIndex = 0;
        this.scriptList = list;
        //发送文件头 （第一个分包文件）
        byte[] b = s8ProtocolMaker.writeScript(dir, fileName, list.get(0));
        postData(b, controlCode);
        Log.i("runScript", "runScript");
    }

    //写入脚本  尾部写文件
    private void writeTrailScript(int dir, String fileName, String script) {
        byte[] b = s8ProtocolMaker.writeTrailScript(dir, fileName, script);
        postData(b);
    }

    //写入文件完毕  发送结束
    private void finishWritScript(int dir, String fileName) {
        byte[] b = s8ProtocolMaker.finishWritScript(dir, fileName);
        postData(b);
    }

    //删除脚本
    public void deleteScript(int dir, String fileName) {
        byte[] b = s8ProtocolMaker.deleteScript(dir, fileName);
        postData(b);
    }

    /**
     写入临时脚本 并执行1次. 脚本会写入临时目录
     @param script   脚本内容
     */
    public void writeAndRunTempScript(String script) {
        writeAndRunTempScript(script, 1000, false,0, 1);
    }

    /**
     写入临时脚本 并执行. 脚本会写入临时目录
     @param script   脚本内容
     @param loopInterval 循环间隔 ms 默认1000
     @param isTime   时长还是次数   YES时长   NO次数
     @param time     时长 (单位s 不是ms)
     @param count    次数
     */
    public void writeAndRunTempScript(String script, int loopInterval, boolean isTime, int time, int count) {
        writeAndRunTempScript(script, loopInterval, isTime, time, count, null);
    }

    public void writeAndRunTempScript(String script, int loopInterval, boolean isTime, int time, int count, OnBleResponseHandler handler) {
        int dir = tempDirCode();
        String fileName = tempFileName();
        writeAndRunScript(dir, fileName, script, loopInterval, isTime, time, count, handler);
    }

    public void runTempScript(int loopInterval, boolean isTime, int time, int count, OnBleResponseHandler handler) {
        int dir = tempDirCode();
        String fileName = tempFileName();

        runScript(dir, fileName, loopInterval, isTime, time, count, handler);
    }

    /**
     写入脚本 并执行
     @param dir      目录
     @param fileName 脚本名
     @param script   脚本内容
     @param loopInterval 循环间隔 ms 默认1000
     @param isTime   时长还是次数   YES时长   NO次数
     @param time     时长 (单位s 不是ms)
     @param count    次数
     */
    public void writeAndRunScript(int dir, String fileName, String script, int loopInterval, boolean isTime, int time, int count) {
        writeAndRunScript(dir, fileName, script, loopInterval, isTime, time, count,null);
    }

    public void writeAndRunScript(int dir, String fileName, String script, int loopInterval, boolean isTime, int time, int count, OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeRunScript;  //先写脚本  再运行脚本 运行完后回调
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        //记录临时数据
        this.shouldRunLastScript = true;
        this.dir = dir;
        this.fileName = fileName;
        this.loopInterval = loopInterval;
        this.isTime = isTime;
        this.time = time;
        this.count = count;

        //由于发球机缓冲区 只有256byte 所以对于脚本可能需要分包
        byte[] fileNameData = stringToBytes(fileName);
        List<String> list = splitScript(script, fileNameData.length);
        if (list.size() == 0) {
            return;
        }

        this.scriptIndex = 0;
        this.scriptList = list;
        //发送文件头 （第一个分包文件）
        byte[] b = s8ProtocolMaker.writeScript(dir, fileName, list.get(0));
        postData(b, controlCode);
    }

    //停止发球脚本
    public void stopScript() {
        stopScript(null);
    }

    public void stopScript(OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeStopScript;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.stopScript();
        postData(b, controlCode);
    }

    //停止发球脚本
    public void pauseScript() {
        pauseScript(null);
    }

    public void pauseScript(OnBleResponseHandler handler) {
        int controlCode = S8ControlCodePauseScript;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.pauseScript();
        postData(b, controlCode);
    }

    //恢复发球脚本
    public void resumeScript() {
        resumeScript(null);
    }

    public void resumeScript(OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeResumeScript;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.resumeScript();
        postData(b, controlCode);
    }

    //获取发球机当前脚本(上一个)的状态
    public void getScriptState(OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeGetScriptState;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.getScriptState();
        postData(b, controlCode);
    }

    //获取发球机的状态
    public void getMachineState(OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeGetMachineState;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.getMachineState();
        postData(b, controlCode);
    }

    //读取机械偏移值
    public void getMachineOffset(OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeGetMachineOffset;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.getMachineOffset();
        postData(b, controlCode);
    }

    //设置机械偏移值
    public void setMachineOffset(int landingPoint, int elevation, int sideSpin, OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeSetMachineOffset;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.setMachineOffset(landingPoint, elevation, sideSpin);
        postData(b, controlCode);
    }

    //保存机械偏移值
    public void saveMachineOffset(OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeSaveMachineOffset;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.saveMachineOffset();
        postData(b, controlCode);
    }

    //获取硬件信息
    public void getFirmwareInfo(OnBleResponseHandler handler) {
        int controlCode = S8ControlCodeGetFirmware;
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        byte[] b = s8ProtocolMaker.getFirmware();
        postData(b, controlCode);
    }

    //临时脚本所在的目录
    private int tempDirCode() {
        return 1;
    }

    //临时脚本的文件名  默认为 dftmp
    private String tempFileName() {
        return "dftmp";
    }

    //写文件
    public void writeFile(int dir, String fileName, String filePath, OnBleResponseHandler handler) {
        writeFile(dir, fileName, filePath, null, handler);
    }

    public void writeFile(int dir, String fileName, String filePath,OnBleWriteFileProgressListner listner, OnBleResponseHandler handler) {
        this.onBleWriteFileProgressListner = listner;

        int controlCode = S8ControlCodeWriteFinish; //写完文件 才回调
        if (handler != null) {
            BleResponseHandlerInfoBean infoBean = new BleResponseHandlerInfoBean(controlCode, handler);
            responseHandlerInfoBeanList.add(infoBean);
        }
        this.shouldRunLastScript = false;
        this.dir = dir;
        this.fileName = fileName;

        //由于发球机缓冲区 只有256byte 所以对于脚本可能需要分包
        byte[] fileNameData = stringToBytes(fileName);
        List<byte[]> list = splitFile(filePath, fileNameData.length);
        if (list.size() == 0) {
            return;
        }

        this.dataIndex = 0;
        this.dataList = list;
        //发送文件头 （第一个分包文件）
        byte[] b = s8ProtocolMaker.writeFile(dir, fileName, list.get(0));
        postData(b, controlCode);
        Log.i("writeFile", "writeFile");
    }

    //写入脚本  尾部写文件
    private void writeFileTrail(int dir, String fileName, byte[] data) {
        byte[] b = s8ProtocolMaker.writeFileTrail(dir, fileName, data);
        postData(b);
    }

    //写入文件完毕  发送结束
    private void finishWriteFile(int dir, String fileName) {
        dataList = null;
        dataIndex = 0;
        byte[] b = s8ProtocolMaker.finishWriteFile(dir, fileName);
        postData(b);
    }

    //endregion

    //region 发送蓝牙数据 与 接受蓝牙数据
    public void postData(byte[] bytes) {
        postData(bytes, S8ControlCodeDefault);
    }

    public void postData(byte[] bytes, int controlCode) {
        if (bytes == null || bytes.length == 0) {
            return;
        }
        if (!DFBleManager.getInstance().canPostData()) {
            distributeData(false, controlCode, 0, null);
            return;
        }
        //发送数据
        DFBleManager.getInstance().postData(bytes);
    }



    //endregion


    //region 校验数据 组包 解包

    //组包的数据
    private static int MAX_SIZE = 1024;
    private byte[] byteBuffer = new byte[MAX_SIZE];  //做一个循环的数组
    private int startIndex = 0; //byteBuffer 循环数组的当前起始下标
    private int endIndex = 0; //byteBuffer 循环数组的当前结束下标

    //解析数据 先组包 在解包
    public void unpack(byte[] data) {
        if (data == null) {
            dealCallback(false, 0, 100, context.getString(R.string.data_packet_empty));
            return;
        }
        //先组包
        for (int i = 0; i < data.length; i++) {
            byteBuffer[endIndex] = data[i];
            endIndex++;
            endIndex %= MAX_SIZE;
        }
        //找包头
        findCommand();
    }

    private void findCommand() {
        while (true) {
            if (startIndex == endIndex) {
                return;
            }
            //先找帧头
            if (byteBuffer[startIndex] != (byte)0xAF) {
                startIndex++;
                startIndex %= MAX_SIZE;
                continue;
            }
            //判断 machineiD
            if (is0AorMachineId()) {
                int len1 = (int)byteBuffer[(startIndex + 1) % MAX_SIZE] & 0xFF;     //将byte转为无符号整形
                int len2 = (int)byteBuffer[(startIndex + 2) % MAX_SIZE] & 0xFF;
                int dataLength = (len1 << 8 | len2) + 3;

                //数据是否足够长 （用实际的指令的长度）
//                if (((startIndex + dataLength) % MAX_SIZE) > endIndex) {
//                    //数据不够长
//                    return;
//                }
                if (startIndex <= endIndex) {
                    if ((startIndex + dataLength) <= endIndex) {
                        //  jump to 'flag'
                    } else {
                        return;
                    }
                } else {
                    //数组循环了
                    if (startIndex + dataLength <= MAX_SIZE) {
                        //   jump to 'flag'
                    } else if (((startIndex + dataLength) % MAX_SIZE) <= endIndex) {
                        //   jump to 'flag'
                    } else {
                        return;
                    }
                }

                //mark： 'flag'
                byte[] data = new byte[dataLength];
                for (int i = 0; i < dataLength; i++) {
                    data[i] = byteBuffer[(startIndex + i) % MAX_SIZE];
                }
                //crc
                byte crc = CRCUtils.calcCrc8(data);
                if (data[dataLength - 1] == crc) {
                    //校验通过
                    //移动 startIndex
                    startIndex += (dataLength);
                    startIndex %= MAX_SIZE;
                    //解析数据
                    parseData(data);
                } else {
                    //校验不通过
                    //移动 startIndex
                    startIndex++;
                    startIndex %= MAX_SIZE;
                }
            } else {
                // machineiD  不通过
                //移动 startIndex
                startIndex++;
                startIndex %= MAX_SIZE;
            }
        }
    }

    //1.是否是'连接发球机'
    //2.判断发球机id是否是 'machineId'
    private boolean is0AorMachineId() {
        if (byteBuffer[(startIndex + 5) % MAX_SIZE] == 0x0A) {
            return true;
        }
        if ((byteBuffer[(startIndex + 3) % MAX_SIZE] == machineID[0] && byteBuffer[(startIndex + 4) % MAX_SIZE] == machineID[1])) {
            return true;
        }
        return false;
    }

    // 解包
    private void parseData(byte[] data) {
        if (data == null) {
            dealCallback(false, 0, 100, context.getString(R.string.data_packet_empty));
            return;
        }
        //根据开发文档 响应数据最少8位
        if (data.length < 8) {
            dealCallback(false, 0, 100, context.getString(R.string.data_packet_empty));
            return;
        }
        byte head = data[0];
        if (head == (byte)0xAF) {
            int len1 = data[1] & 0xFF;     //将byte转为无符号整形
            int len2 = data[2] & 0xFF;
            int len = len1 << 8 | len2;

            //控制命令
            int controlCode = data[5];
            //状态位
            int status = data[6];
            if (status != 0) {
                Log.e("蓝牙响应数据", "状态错误"+status);
                dealCallback(false, controlCode, status, getStatusDesc(status));
                return;
            }

            Object res = null;
            switch (controlCode) {
                //读文件内容
                case 0x06: {
                    // 判断数据长度，防止数组越界
                    if (data.length == len + 3) {
                        //从 9 开始 ， 长度为 len - 7
                        byte[] content = new byte[len - 7];
                        System.arraycopy(data, 9, content, 0, len - 7);
                        String text = bytesToString(content);
                        res = text;
                        Log.i("读文件内容成功 0x06", text);
                    }
                }
                break;
                //获取文件目录下文件名
                case 0x07: {
                    // 判断数据长度，防止数组越界
                    if (data.length == len + 3) {
                        //从 10 开始 ， 长度为 len - 8
                        byte[] content = new byte[len - 8];
                        System.arraycopy(data, 10, content, 0, len - 8);
                        String text = bytesToString(content);
                        res = text;
                        Log.i("获取文件目录下文件名 0x07", text);
                    }
                }
                break;
                //心跳
                case 0x09: {
                    Log.i("心跳 0x09","");
                }
                break;

                case 0x10: {
                    // 判断数据长度，防止数组越界
                    if (data.length == len + 3) {
                        int hor1 = data[7] & 0xFF;
                        int hor2 = data[8] & 0xFF;
                        int hor = hor1 << 8 | hor2;

                        int ver1 = data[9] & 0xFF;
                        int ver2 = data[10] & 0xFF;
                        int ver = ver1 << 8 | ver2;

                        int rotation1 = data[11] & 0xFF;
                        int rotation2 = data[12] & 0xFF;
                        int rotation = rotation1 << 8 | rotation2;

                        BleOffsetBean bean = new BleOffsetBean();
                        bean.setLandingPointOffset(hor);
                        bean.setElevationOffset(ver);
                        bean.setSideSpinOffset(rotation);
                        res = bean;
                    }
                }
                break;
                //连接发球机
                case 0x0A: {
                    machineID[0] = data[3];
                    machineID[1] = data[4];
                    s8ProtocolMaker.setMachineID(data[3], data[4]);
                    Log.i("连接发球机 0x0A","");
                    if (data.length != 8) {
                        //超8的协议返回长度是8 超6的协议包含了name mac等
                        //name
                        byte[] nameBytes = new byte[14];
                        System.arraycopy(data, 7, nameBytes, 0, 14);
                        String name = bytesToString(nameBytes);

                        //mac
                        byte[] macBytes = new byte[6];
                        System.arraycopy(data, 21, macBytes, 0, 6);
                        StringBuilder builder = new StringBuilder();
                        for (int i = 0; i < 6; i++) {
                            String hex = byteToHex(macBytes[i]);
                            builder.append(hex);
                            if (i % 2 == 1) {
                                builder.append(":");
                            }
                        }
                        String mac = builder.toString().toUpperCase();

                        BleMachineInfoBean infoBean = new BleMachineInfoBean();
                        infoBean.setMachineId(machineID[0] << 8 | machineID[1]) ;
                        infoBean.setType(2);
                        infoBean.setMac(mac);
                        infoBean.setName(name);
//                        infoBean.setVersion("1.1.4");

                        res = infoBean;
                    }
                }
                break;
                //启动脚本
                case 0x0B: {
                    Log.i("启动脚本 0x0B","");
                }
                break;

                case 0x17: {

                }
                break;

                //获取机器id
                case 0x0F: {
                    machineID[0] = data[3];
                    machineID[1] = data[4];
                    s8ProtocolMaker.setMachineID(data[3], data[4]);
                }
                break;

                //测试发球
                case 0x13: {
                    Log.i("测试发球",  "");
                }
                break;

                //获取固件版本
                case 0x14: {
                    //从 8 开始 ， 长度为 len - 6
                    byte[] content = new byte[len - 6];
                    System.arraycopy(data, 8, content, 0, len - 6);
                    String version = bytesToString(content);
                    res = version;
                }
                break;

                //读取发球机脚本的状态
                case 0x19: {
                    if (data.length == len + 3) { // 判断数据长度，防止数组越界
                        //机器状态
                        int deviceStatus = data[7];
                        //计划发球时长
                        int planTime1 = data[8] & 0xFF;
                        int planTime2 = data[9] & 0xFF;
                        int planTime3 = data[10] & 0xFF;
                        int planTime4 = data[11] & 0xFF;
                        int planTime = (planTime1 << 24) | (planTime2 << 16) | (planTime3 << 8) | planTime4;
                        //总发球时长
                        int totalTime1 = data[12] & 0xFF;
                        int totalTime2 = data[13] & 0xFF;
                        int totalTime3 = data[14] & 0xFF;
                        int totalTime4 = data[15] & 0xFF;
                        int totalTime = (totalTime1 << 24) | (totalTime2 << 16) | (totalTime3 << 8) | totalTime4;
                        //脚本发球个数
                        int count1 = data[16] & 0xFF;
                        int count2 = data[17] & 0xFF;
                        int count = count1 << 8 | count2;
                        //已发球数
                        int servedCount1 = data[18] & 0xFF;
                        int servedCount2 = data[19]& 0xFF;
                        int servedCount = servedCount1 << 8 | servedCount2;
                        //计划发球总数
                        int planCount1 = data[26] & 0xFF;
                        int planCount2 = data[27] & 0xFF;
                        int planCount = planCount1 << 8 | planCount2;

                        BleScriptStatusBean bean = new BleScriptStatusBean();
                        bean.setStatus(deviceStatus);
                        bean.setPlanTime(planTime);
                        bean.setTotalTime(totalTime);
                        bean.setCount(count);
                        bean.setPlanCount(planCount);
                        bean.setServedCount(servedCount);
                        res = bean;
                    }
                }
                break;

                default:
                    break;
            }
//            Log.i("unpack", "len=" + len + " machineId=" + machineId + " code=" + controlCode + " status=" + status);
            dealCallback(true, controlCode, status, res);
        } else {
            Log.i("unpack", "error");
            dealCallback(false, 0, 101, context.getString(R.string.data_packet_error));
        }
    }

    private String getStatusDesc(int status) {
        switch(status) {
            case 0:
                return context.getString(R.string.success);
            case 1:
                return context.getString(R.string.verification_error);
            case 2:
                return context.getString(R.string.error_id);
            case 3:
                return context.getString(R.string.command_error);
            case 4:
                return context.getString(R.string.data_struct_error);
            case 5:
                return context.getString(R.string.execution_error);
            case 6:
                return context.getString(R.string.data_error);

            default:
                return null;
        }
    }


    private void dealCallback(boolean success, int controlCode, int statusCode, Object res) {
        if (success) {
//            self.time = 0; //重置心跳时间计数
//            if (code == DFParseDataResCodeConnectMachine) {
//            [self startHeartBeat];
//            } else if (code == DFParseDataResCodeDisConnectMachine) {
//            [self stopHeartBeat];
//            }

//            if (statusCode == BTControlProtocolUtils.S8ControlCodeConnectMachine) {
//
//            } else if (statusCode == BTControlProtocolUtils.S8ControlCodeDisConnectMachine) {
//
//            }
//            else
            if (controlCode == S8ControlCodeWriteHead || controlCode == S8ControlCodeWriteTrail) {
                if (dataList != null && dataList.size() > 0) {
                    dataIndex++;
                    if (dataIndex >= dataList.size()) {
                        //文件写完了
                        finishWriteFile(dir, fileName);
                    } else {
                        if (this.onBleWriteFileProgressListner != null) {
                            int progress = scriptIndex * 100 / scriptList.size();
                            this.onBleWriteFileProgressListner.onProgress(progress);
                        }
                    }
                } else {
                    scriptIndex++;
                    if (scriptIndex >= scriptList.size()) {
                        //脚本写完了
                        finishWritScript(dir, fileName);
                    } else {
                        //继续写尾部
                        String script = scriptList.get(scriptIndex);
                        writeTrailScript(dir, fileName, script);
                    }
                }
            }
            else if (controlCode == S8ControlCodeWriteFinish) {
                this.onBleWriteFileProgressListner = null;
                //写文件完毕 看是否需要运行脚本
                if (shouldRunLastScript) {
                    //需要运行脚本的 在运行完脚本会再回调
                    runScript(dir, fileName, loopInterval, isTime, time, count);
                    shouldRunLastScript = false;
                } else {
                    //写完文件 有可能需要回调
                    distributeData(true, controlCode, statusCode, res);
                }
            }

            else {
                // mark: -  不需要对回调结果进行特殊处理的,在这里进行通用处理
                distributeData(true, controlCode, statusCode, res);
            }
        } else {
            distributeData(false, controlCode, statusCode, res);
        }
    }

    //分发数据
    private void distributeData(boolean success, int controlCode, int statusCode, Object res) {
        //controlCode == 0
        if (controlCode == S8ControlCodeDefault) {
            if ((res instanceof String) && context != null) {
                ToastUtil.showToast(context, (String) res);
            }
            return;
        }
        List<BleResponseHandlerInfoBean> tmpList = new ArrayList<>();
        for (BleResponseHandlerInfoBean bean:responseHandlerInfoBeanList) {
            if (controlCode == bean.getControlCode()) {
                OnBleResponseHandler handler = bean.getHandler();
                if (handler != null) {
                    //回调完毕后需要删除
                    boolean b = handler.onResponse(success, statusCode, res);
                    if (!b) {
                        tmpList.add(bean);
                    }
                }
            }
        }
        if (tmpList.size() > 0) {
            responseHandlerInfoBeanList.removeAll(tmpList);
        }
    }

    private String bytesToString(byte[] bytes) {
        try {
            return new String(bytes, "GB18030");
        } catch (UnsupportedEncodingException exception) {
            exception.printStackTrace();
            return null;
        }
    }

    private byte[] stringToBytes(String str) {
        try {
            return str.getBytes("GB18030");
        }catch (UnsupportedEncodingException exception) {
            exception.printStackTrace();
            return null;
        }
    }

    //byteToHex
    private String byteToHex(byte b) {
        int num = (int)b;
        if (num < 0) {
            num += 256;
        }
        String hex = Integer.toHexString(num);
        return hex;
    }

    //endregion


    //region  脚本的分包

    /*
    @param script  脚本
    @param fileNameLenth 文件名的长度，文件名可能中文
    */
    private List<String> splitScript(String script, int fileNameLenth) {
        List<String> list = new ArrayList<>();
        //由于发球机缓冲区只有256byte 对脚本分包
        //包头，机器码，校验码等其他数据（不包含文件名）11个字节 （安全起见，减掉20）
        //script没有中文字符
//        int length = 256 - 20 - fileNameLenth;
//        经过ios测试，可以用1000
        int length = 1000 - 20 - fileNameLenth;

        //脚本足够短， 不需要分包
        if (script.length() < length) {
            list.add(script);
            return list;
        }

        int size = script.length() / length + (script.length() % length == 0 ? 0 : 1);
        for (int i = 0; i < size; i++) {
            if (i == size - 1) {
                //最后一个
                String substr = script.substring(i * length);
                list.add(substr);
            } else {
                String substr = script.substring(i * length, (i + 1) * length);
                list.add(substr);
            }
        }
        return list;
    }

    /*
        @param filePath  文件的路径
        @param fileNameLenth 文件名的长度，文件名可能中文
        */
    private List<byte[]> splitFile(String filePath, int fileNameLenth) {
        File file = new File(filePath);
        if (!file.exists()) {
            return null;
        }

        List<byte[]> list = new ArrayList<>();
        //由于发球机缓冲区只有256byte 对脚本分包
        //包头，机器码，校验码等其他数据（不包含文件名）11个字节 （安全起见，减掉20）
//        int length = 256 - 20 - fileNameLenth;
//        经过ios测试，可以用1000
        int length = 1000 - 20 - fileNameLenth;
        try {
            FileInputStream fileInputStream = new FileInputStream(file);

            int fileLength = (int) file.length();  //固件文件不大 用int就可以
            //脚本足够短， 不需要分包
            if (file.length() < length) {
                byte[] data = new byte[fileLength];
                fileInputStream.read(data);
                fileInputStream.close();
                list.add(data);
                return list;
            }
            //分包
            int size = fileLength / length + (fileLength % length == 0 ? 0 : 1);
            for (int i = 0; i < size; i++) {
                if (i == size - 1) {
                    //最后一个
                    int lastLength = fileLength - i * length;
                    byte[] data = new byte[lastLength];
                    fileInputStream.read(data, i * length, lastLength);
                    fileInputStream.close();
                    list.add(data);
                } else {
                    byte[] data = new byte[length];
                    fileInputStream.read(data, i * length, length);
                    list.add(data);
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }
    //endregion

}
